use crate::AppState;
use ane_system::dao::{RoleSimpletVo, UserId};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use sea_orm::DbErr;
use serde::{Deserialize, Serialize};

mod backbend;
mod login_user;
pub use backbend::AneBackend;
pub use login_user::LoginUser;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Claims {
    // aud: String, // Optional. Audience
    exp: u64, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp)
    iat: u64, // Optional. Issued at (as UTC timestamp)
    iss: String, // Optional. Issuer
    nbf: u64, // Optional. Not Before (as UTC timestamp)
    sub: String, // Optional. Subject (whom token refers to)
    uid: String, // Optional. Subject (whom token refers to)
}

#[derive(Clone)]
pub struct JwtManager {
    encoder_key: EncodingKey,
    decoder_key: DecodingKey,
    access_token_expires: u64,
    header: Header,
    validation: Validation,
}

impl JwtManager {
    pub fn new() -> Self {
        let alg = Algorithm::default();
        let mut validation = Validation::new(alg);
        validation.set_audience(&["items"]);
        let header = Header::new(alg);
        Self {
            encoder_key: EncodingKey::from_secret("secret".as_ref()),
            decoder_key: DecodingKey::from_secret("secret".as_ref()),
            access_token_expires: 3600,
            header,
            validation,
        }
    }

    pub fn encode<S: ToString>(&self, uid: S) -> jsonwebtoken::errors::Result<String> {
        let timestamp = jsonwebtoken::get_current_timestamp();
        let my_claims = Claims {
            exp: timestamp + self.access_token_expires,
            iat: timestamp,
            nbf: timestamp,
            uid: uid.to_string(),
            ..Claims::default()
        };
        jsonwebtoken::encode(&self.header, &my_claims, &self.encoder_key)
    }

    pub fn decode(&self, token: &str) -> jsonwebtoken::errors::Result<String> {
        jsonwebtoken::decode::<Claims>(token, &self.decoder_key, &self.validation)
            .map(|x| x.claims.uid)
    }
}

#[test]
fn test_jwt() {
    let jwt = JwtManager::new();
    let id1 = "adsfasdfasd";
    let token = jwt.encode(id1).unwrap();
    let id2 = jwt.decode(&token).unwrap();
    assert_eq!(id1, id2);
}

pub struct Security;

impl Security {
    /// 生产user在cache中的key
    #[inline]
    fn generate_key(id: &UserId) -> String {
        format!("c_user_{id}")
    }

    /// 清除user保存在cache中的缓存
    pub async fn clean_users_cache(state: &AppState, ids: Vec<UserId>) {
        let cache = state.cache();
        for id in ids {
            let key = Self::generate_key(&id);
            cache.del(&key).await;
        }
    }

    /// 加载登陆的用户信息, 优先读取cache, 在读取数据库
    pub async fn load_user(state: &AppState, id: &UserId) -> Option<LoginUser> {
        let cache = state.cache();
        let key = Self::generate_key(id);
        let s: Option<String> = cache.get(&key).await;
        if let Some(st) = s {
            let user = serde_json::from_str::<LoginUser>(&st).ok();
            if user.is_some() {
                return user;
            }
        }
        let user =
            ane_system::dao::User::get_detail_by_id(state.db(), id.clone(), None, None, false)
                .await
                .ok()
                .flatten()
                .map(Into::into);
        if user.is_some() {
            let _ = cache
                .set_ex(&key, serde_json::to_string(&user).unwrap(), 1000 * 1200)
                .await;
        }
        return user;
    }

    pub async fn load_user_by_pawwsord(
        state: &AppState,
        name: &str,
        password: &str,
    ) -> Result<Option<(LoginUser, bool)>, DbErr> {
        let (user, verity) = if let Some(u) =
            ane_system::dao::User::get_by_name_password(state.db(), name, password).await?
        {
            u
        } else {
            return Ok(None);
        };
        let roles = ane_system::dao::User::get_roles::<RoleSimpletVo>(state.db(), user.user_id)
            .await?
            .into_iter()
            .map(Into::into)
            .collect::<Vec<_>>();
        Ok(Some((
            LoginUser {
                id: user.user_id,
                d_id: user.dept_id,
                name: user.username,
                nickname: user.nickname,
                status: user.status,
                roles,
            },
            verity,
        )))
    }
}
